﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using DefensiveShield.Properties;

namespace DefensiveShield
{
    public static partial class OS
    {
        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- CRYPTODLL LOAD -------------
        //---------------------------------------------------------------------------------------------------------------------------------

        [DllImport("CryptoDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void CryptoDllGenerateKey(IntPtr key, ref int keySize);

        [DllImport("CryptoDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void CryptoDllGenerateHashIV(IntPtr key, ref int keySize);

        [DllImport("CryptoDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void CryptoDllHash(IntPtr input, ref int inputSize, IntPtr output, ref int outputSize, IntPtr IV);

        [DllImport("CryptoDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void CryptoDllEncrypt(IntPtr input, ref int inputSize, IntPtr output, ref int outputSize, IntPtr key, ref int keySize);

        [DllImport("CryptoDll.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void CryptoDllDecrypt(IntPtr input, ref int inputSize, IntPtr output, ref int outputSize, IntPtr key, ref int keySize);

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- CONNECT CRYPTO DLL ---------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция, предназначенная для генерации ключа по введённому паролю
             * 
             * Данная функция хеширует пароль, и генерирует пробный ключ, чтобы узнать его длинную, после чего хеш либо обрезается по длинне ключа, либо рекурсивно заполняет весь ключ по всей его длинне
             * 
            @return string - строка, являющаяся ключём шифрования
            @return null - в случае ошибки
        */

        private static string ConstructKeyFromPassword(string passwd)
        {
            try
            {
                var hash = Hash(passwd);
                hash = Hash(hash, hash); // Because we want to have pseudo-unique IV
                var hashBytes = Convert.FromBase64String(hash);
                var keyLength = Convert.FromBase64String(GenerateKey()).Length;
                var keyBytes = new byte[keyLength];
                var j = 0;
                for (var i = 0; i < keyLength; i++)
                {
                    if (j >= hashBytes.Length)
                        j = 0;
                    keyBytes[i] = hashBytes[j];
                    j++;
                }

                var key = Convert.ToBase64String(keyBytes);
                return key;
            }
            catch (Exception exception)
            {
                MessageBox.Show("Неизвестная ошибка при попытке сгенерировать ключ по данному паролю.");
                return null;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- CONNECT CRYPTO DLL ---------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция, предназначенная для проверки подключения к динамически подключаемой библиотеке шифрования
             * 
             * Данная функция тестирует каждую возможность, которую должна уметь педоставлять библиотека шифрования
             * 
            @return ErrorCodes - возвращает код ошибки
        */
        private static ErrorCodes CheckCryptoDllConnection()
        {
            try
            {
                var key = GenerateKey();
                if (key == null)
                    return ErrorCodes.CryptoDllError;
                var iv = GenerateHashIV();
                if (iv == null)
                    return ErrorCodes.CryptoDllError;
                var str = "This is message for dno CryptoDll testing.";
                var hash = Hash(str, iv);
                if (hash == null)
                    return ErrorCodes.CryptoDllError;
                var cipher = Encrypt(str, key);
                if (cipher == null)
                    return ErrorCodes.CryptoDllError;
                var plain = Decrypt(cipher, key);
                if (plain == null)
                    return ErrorCodes.CryptoDllError;

                if (str != plain)
                {
                    LogMessage("", "CryptoDll не прошла проверку на пригодность. Функции дешифрования не является обратной к функции шифрования");
                    return ErrorCodes.CryptoDllEncryptionDecryptionCorrupt;
                }

                return ErrorCodes.NoError;
            }
            catch (Exception exception)
            {
                MessageBox.Show("Неизвестная ошибка при тестировании файла CryptoDll.dll");
                return ErrorCodes.CryptoDllError;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- HASH -----------------------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция генерации ключа шифрования
             * 
             * Данная функция подключает динамическую библиотеку, содержащую функцию генерации ключа шифрования и выполняет генерацию
             * Важное ограничение - длинна ключа не должна превышать 16354 бит. При попытке записи ключа сверх этого размера, поведение может быть не предсказуемым.
             * 
            @return string - ключ шифрования
            @return null - в случае ошибки
        */
        public static string GenerateKey()
        {
            try
            {
                var KEY_LENGTH_LIMIT = 2048;

                //var key = new StringBuilder(KEY_LENGTH_LIMIT);
                var key = Marshal.AllocHGlobal(KEY_LENGTH_LIMIT);
                int keySize = 0;
                CryptoDllGenerateKey(key, ref keySize);

                var keyBytes = new byte [keySize];
                for (var i = 0; i < keySize; i++)
                    keyBytes[i] = Marshal.ReadByte(IntPtr.Add(key, i * sizeof(byte)));
                Marshal.FreeHGlobal(key);
                return Convert.ToBase64String(keyBytes, 0, keySize);
            }
            catch (System.DllNotFoundException exception)
            {
                MessageBox.Show("Файл CryptoDll.dll не найден.");
                return null;
            }
            catch (Exception exception)
            {
                MessageBox.Show("Неизвестная ошибка при работе с файлом для шифрования CryptoDll.dll");
                return null;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- HASH -----------------------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция генерации инициализационного вектора хеширования
             * 
             * Данная функция подключает динамическую библиотеку, содержащую функцию генерации инициализационного вектора хешированияпроизводит генерацию
             * Важное ограничение - длинна инициализационного вектора не должна превышать 16354 бит. При попытке записи величины сверх этого размера, поведение может быть не предсказуемым.
             * 
            @return string - инициализационный вектор
            @return null - в случае ошибки
        */
        public static string GenerateHashIV()
        {
            try
            {
                var IV_LENGTH_LIMIT = 2048;

                //var key = new StringBuilder(KEY_LENGTH_LIMIT);
                var iv = Marshal.AllocHGlobal(IV_LENGTH_LIMIT);
                int ivSize = 0;
                CryptoDllGenerateHashIV(iv, ref ivSize);

                var ivBytes = new byte[ivSize];
                for (var i = 0; i < ivSize; i++)
                    ivBytes[i] = Marshal.ReadByte(IntPtr.Add(iv, i * sizeof(byte)));
                Marshal.FreeHGlobal(iv);
                return Convert.ToBase64String(ivBytes, 0, ivSize);
            }
            catch (System.DllNotFoundException exception)
            {
                MessageBox.Show("Файл CryptoDll.dll не найден.");
                return null;
            }
            catch (Exception exception)
            {
                MessageBox.Show("Неизвестная ошибка при работе с файлом для шифрования CryptoDll.dll");
                return null;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- GENERATE RANDOM STRING -----
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция генерации случайной строки требуемой длинны
             * 
             * Данная функция предназначена для генерации случайных строк необходимой длинны
             * 
            @return string - случайная строка заданной длинны
            @return null - в случае ошибки
        */
        public static string GenerateRandomString(int length)
        {
            try
            {
                //var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
                var random = new Random();
                var result = new string(
                    Enumerable.Repeat(1, length)
                        .Select(s => (char) random.Next(-128, 127))
                        .ToArray());
                return result;
            }
            catch (Exception exception)
            {
                LogMessage("", "Неизвестная ошибка при генерации случайной строки заданной длинны.");
                return null;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- HASH -----------------------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция хеширования
             * 
             * Данная функция подключает динамическую библиотеку, содержащую функцию хеширования и выполняет хеширование при необходимости
             * Важное ограничение 1 - функция не предназначена на работу с функцией хеширования, возвращающей хеш длинны более чем 16384 бит. При использовании функции, использующей более длинные хеши - поведение может быть непредсказуемым.
             * Важное ограничение 2 - Инициализационный вектор IV используемый в операции хеширования имеет длинну 512 бит. Выбирается произвольно для каждой отдельной установки программы.
             * 
            @return string - хеш строки
            @return null - в случае ошибки
        */
        public static string Hash(string str, string IVDebug=null)
        {
            try
            {
                var BYTELIMIT = 2048;

                var byteConverter = new UnicodeEncoding();

                var inputBytes = byteConverter.GetBytes(str);
                int inputSize = inputBytes.Length;
                var input = Marshal.AllocHGlobal(inputSize);
                for (var i = 0; i < inputSize; i++)
                    Marshal.WriteByte(IntPtr.Add(input, i * sizeof(byte)), inputBytes[i]);

                var output = Marshal.AllocHGlobal(BYTELIMIT);
                int outputSize = 0;
                
                var ivBytes = Convert.FromBase64String(IVDebug ?? IV);
                var iv = Marshal.AllocHGlobal(ivBytes.Length);
                for (var i = 0; i < ivBytes.Length; i++)
                    Marshal.WriteByte(IntPtr.Add(iv, i * sizeof(byte)), ivBytes[i]);

                CryptoDllHash(input, ref inputSize, output, ref outputSize, iv);
                Marshal.FreeHGlobal(input);
                Marshal.FreeHGlobal(iv);

                var outputBytes = new byte[outputSize];
                for (var i = 0; i < outputSize; i++)
                    outputBytes[i] = Marshal.ReadByte(IntPtr.Add(output, i * sizeof(byte)));
                Marshal.FreeHGlobal(output);
                return Convert.ToBase64String(outputBytes, 0, outputSize);

//                 var input = str; //new StringBuilder(str);
//                 var inputSize = input.Length;
//                 var output = new StringBuilder(BYTELIMIT);
//                 CryptoDllHash(input, ref inputSize, output, IV);
//                 var hash = output.ToString(

                // This is implementation of really good hash function - SHA512
                // var byteConverter = new UnicodeEncoding();
                // var hash = byteConverter.GetString(System.Security.Cryptography.SHA512.Create().ComputeHash(byteConverter.GetBytes(str)));
            }
            catch (System.DllNotFoundException exception)
            {
                MessageBox.Show("Файл CryptoDll.dll не найден.");
                return null;
            }
            catch (Exception exception)
            {
                MessageBox.Show("Неизвестная ошибка при работе с файлом для шифрования CryptoDll.dll");
                return null;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- ENCRYPT --------------------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция шифрования
             * 
             * Данная функция используя динамическую библиотеку, содержащую функцию шифрования, шифрует строку на заданном ключе
             * Функция имеет важное ограничение - шифрование должно возвращать шифротекст не более чем в 2 раза длинне, по сравнению с самим шифруемым текстом. В ином случае поведение непредсказуемо.
             * 
            @return string - зашифрованную строку
            @return null - в случае ошибки
        */

        public static string Encrypt(string str, string strkey)
        {
            try
            {
                var MULTIPLICATION_KOEF = 2;

                // This crutch has been made in security measures, so attacker will not see encrypted text for only padding in open text
                if (str == "")
                    return "";

                var byteConverter = new UnicodeEncoding();

                var inputBytes = byteConverter.GetBytes(str);
                int inputSize = inputBytes.Length;
                var input = Marshal.AllocHGlobal(inputSize);
                for (var i = 0; i < inputSize; i++)
                    Marshal.WriteByte(IntPtr.Add(input, i * sizeof(byte)), inputBytes[i]);

                var output = Marshal.AllocHGlobal(inputSize * MULTIPLICATION_KOEF);
                int outputSize = 0;

                var keyBytes = Convert.FromBase64String(strkey);
                int keySize = keyBytes.Length;
                var key = Marshal.AllocHGlobal(keySize);
                for (var i = 0; i < keySize; i++)
                    Marshal.WriteByte(IntPtr.Add(key, i * sizeof(byte)), keyBytes[i]);

                CryptoDllEncrypt(input, ref inputSize, output, ref outputSize, key, ref keySize);
                Marshal.FreeHGlobal(input);
                Marshal.FreeHGlobal(key);

                var outputBytes = new byte[outputSize];
                for (var i = 0; i < outputSize; i++)
                    outputBytes[i] = Marshal.ReadByte(IntPtr.Add(output, i * sizeof(byte)));
                Marshal.FreeHGlobal(output);
                return Convert.ToBase64String(outputBytes, 0, outputSize);



//                 var input = str;//new StringBuilder(str);
//                 var inputSize = input.Length;
//                 var strkey = key;//new StringBuilder(key);
//                 var strkeySize = strkey.Length;
//                 var output = new StringBuilder(str.Length * MULTIPLICATION_KOEF);
//                 var outputSize = 0;
//                 CryptoDllEncrypt(input, ref inputSize, output, ref outputSize, strkey, ref strkeySize);
//                 var result = output.ToString();//(0, outputSize);
//                 return result;
            }
            catch (System.DllNotFoundException exception)
            {
                MessageBox.Show("Файл CryptoDll.dll не найден.");
                return null;
            }
            catch (Exception exception)
            {
                MessageBox.Show("Неизвестная ошибка при работе с файлом для шифрования CryptoDll.dll");
                return null;
            }
        }

        //---------------------------------------------------------------------------------------------------------------------------------
        //---------------------------------------------------------------------------------------------------- DECRYPT --------------------
        //---------------------------------------------------------------------------------------------------------------------------------
        /**
            @brief Функция дешифрования
             * 
             * Данная функция используя динамическую библиотеку, содержащую функцию дешифрования, дешифрует строку на заданном ключе
             * Функция имеет важное ограничение - расшифрованный текст должен быть не более чем в 2 раза длиннее шифротекста, иначе функция может иметь непредсказуемые ограничения.
             * 
            @return string - расшифрованную строку
            @return null - в случае ошибки
        */
        public static string Decrypt(string str, string strkey)
        {
            try
            {
                var MULTIPLICATION_KOEF = 2;

                // This crutch has been made in security measures, so attacker will not see encrypted text for only padding in open text
                if (str == "")
                    return "";

                var byteConverter = new UnicodeEncoding();

                var inputBytes = Convert.FromBase64String(str);
                int inputSize = inputBytes.Length;
                var input = Marshal.AllocHGlobal(inputSize);
                for (var i = 0; i < inputSize; i++)
                    Marshal.WriteByte(IntPtr.Add(input, i * sizeof(byte)), inputBytes[i]);

                var output = Marshal.AllocHGlobal(inputSize * MULTIPLICATION_KOEF);
                int outputSize = 0;

                var keyBytes = Convert.FromBase64String(strkey);
                int keySize = keyBytes.Length;
                var key = Marshal.AllocHGlobal(keySize);
                for (var i = 0; i < keySize; i++)
                    Marshal.WriteByte(IntPtr.Add(key, i * sizeof(byte)), keyBytes[i]);

                CryptoDllDecrypt(input, ref inputSize, output, ref outputSize, key, ref keySize);
                Marshal.FreeHGlobal(input);
                Marshal.FreeHGlobal(key);

                var outputBytes = new byte[outputSize];
                for (var i = 0; i < outputSize; i++)
                    outputBytes[i] = Marshal.ReadByte(IntPtr.Add(output, i * sizeof(byte)));
                Marshal.FreeHGlobal(output);
                var text = byteConverter.GetString(outputBytes);
                // It is important - text will contain some trash at the end (specially - '\0'), because of block alignment in encryption
                //return text.Remove(text.IndexOf('\0'));
                return text;

//                 var input = str; //new StringBuilder(result);
//                 var inputSize = input.Length;
//                 var strkey = key; //new StringBuilder(key);
//                 var strkeySize = strkey.Length;
//                 var output = new StringBuilder(str.Length * MULTIPLICATION_KOEF);
//                 var outputSize = 0;
//                 CryptoDllDecrypt(input, ref inputSize, output, ref outputSize, strkey, ref strkeySize);
//                 var result = output.ToString();//(0, outputSize);
//                 return result;
            }
            catch (System.DllNotFoundException exception)
            {
                MessageBox.Show("Файл CryptoDll.dll не найден.");
                return null;
            }
            catch (Exception exception)
            {
                MessageBox.Show("Неизвестная ошибка при работе с файлом для шифрования CryptoDll.dll");
                return null;
            }
        }
    }
}
